// This File is part of the "nyaqt" Project
// 
// Copyright  2009 sometmes@gmail.com
// All rights reserved
// 
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.



using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Drawing;

namespace nyaqt
{
    class StatementExecutionController
    {
        private BackgroundWorker2 _execWorker = new BackgroundWorker2();

        public StatementExecutionController()
        {
            _execWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(execWorker_DoWork);
            _execWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(execWorker_RunWorkerCompleted);
            _execWorker.WorkerReportsProgress = true;
            _execWorker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(execWorker_ProgressChanged);
        }

        DateTime _startTime = DateTime.MinValue;
        DateTime _endTime = DateTime.MinValue;
        public TimeSpan ExecDuration
        {
            get
            {
                if (_endTime == DateTime.MinValue && _startTime!=DateTime.MinValue)
                    return DateTime.Now.Subtract(_startTime);
                else
                    return _endTime.Subtract(_startTime);
            }
        }

        IDbConnection _connection;
        public IDbConnection Connection
        {
            get { return _connection; }
        }

        ConnectionInfo _connectionInfo;
        public ConnectionInfo ConnectionInfo
        {
            get
            {
                return _connectionInfo;
            }
            set
            {
                if (value.Current == null)
                    throw new SystemException();
                _connectionInfo = value;
                _connection = value.Current;
            }
        }

        string _stm;
        int _regionStart = -1;
        int _regionEnd = -1;
        int _commandTimeout;
        nyaqt.Plugins.TestCommand _testCommand = null;
        DataSet _dataset;

        public DataSet DataSet
        {
            get { return _dataset; }
        }

        public void ExecuteAsync(string stm)
        {
            ExecuteAsync(stm, new Point(0, stm.Length - 1), null);
        }

        public void ExecuteAsync(string script, Point region, nyaqt.Plugins.TestCommand testcmd)
        {
            _startTime = DateTime.Now;
            _endTime = DateTime.MinValue;
            if (Start != null)
                Start.Invoke();

            _stm = script;
            _regionStart = region.X;
            _regionEnd = region.Y;
            _testCommand = testcmd;
            //_stm = "select * from AdventureWorks.Person.AddressType";
            //_stm = "select * from AdventureWorks.Production.WorkOrder";
            _commandTimeout = My.Settings.CommandTimeout;
            _dataset = new DataSet();

            _execWorker.RunWorkerAsync();

        }

        public void Cancel()
        {
            //try Cancel
            throw new NotImplementedException();
        }

        public void Abort()
        {
            _execWorker.Abort();
            _connection = null;
            _endTime = DateTime.Now;
            if (End != null)
                End.Invoke();
        }

        protected DataTable BuildDataTable(string tablename, DataTable schema)
        {
            DataTable table = null;
            lock (_dataset)
            {
                table = _dataset.Tables.Add(tablename);
                List<DataColumn> primaryKeys = new List<DataColumn>();

                foreach (DataRow schemarow in schema.Rows)
                {
                    DataColumn col = table.Columns.Add();
                    string colName = (string)schemarow["ColumnName"];
                    if (!string.IsNullOrEmpty(colName) && !table.Columns.Contains(colName))
                        col.ColumnName = colName;
                    else
                        schemarow["ColumnName"] = col.ColumnName; // If DataReader returns anonymous column (i.e. aggregate) we use generated DataTable columnName.
                    col.DataType = (Type)schemarow["DataType"];
                    col.AllowDBNull = (bool)schemarow["AllowDBNull"];
                    col.AutoIncrement = (bool)schemarow["IsAutoIncrement"];
                    col.ReadOnly = (bool)schemarow["IsReadOnly"];
                    col.Unique = (bool)schemarow["IsUnique"];
                    if (schemarow["IsKey"] != DBNull.Value && (bool)schemarow["IsKey"] == true)
                        primaryKeys.Add(col);
                }
                if (primaryKeys.Count > 0)
                    table.Constraints.Add(tablename + "_PK", primaryKeys.ToArray(), true);
            }
            return table;
        }


        void execWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            string declare;
            string script;
            TextUtils.SplitScriptParts(out declare, out script, _stm, _regionStart, _regionEnd);
            string[] stms = TextUtils.SplitBatches(script);
            bool hasrows = false;
            int lineoffset = _stm.Substring(0,_regionStart).Split('\n').Length - 1;
            try
            {
                IDbCommand cmd;
                if (_testCommand == null)
                {
                    cmd = _connection.CreateCommand();
                    cmd.CommandTimeout = _commandTimeout;
                    cmd.Connection = _connection;
                }
                else
                    cmd = _testCommand.Command;
                _connectionInfo.ConnectionProperties.ConnectionMessage += new nyaqt.Plugins.ConnectionMessageDelegate(ConnectionProperties_ConnectionMessage);
                foreach (string stm in stms)
                {
                    cmd.CommandText = declare + stm;
                    IDataReader r = cmd.ExecuteReader(CommandBehavior.KeyInfo);
                    _execWorker.ReportProgress(0, "ReaderExecuted");
                    do
                    {
                        DataTable schematable = r.GetSchemaTable();
                        if (schematable != null && schematable.Rows.Count > 0)
                        {
                            DataTable table = BuildDataTable("Table" + _dataset.Tables.Count, schematable);
                            ExecuteNewResultEventArgs ee = new ExecuteNewResultEventArgs();
                            ee.Schema = schematable;
                            ee.Data = table;
                            _execWorker.ReportProgress(1, ee);

                            while (r.Read())
                            {
                                hasrows = true;
                                object[] values = new object[r.FieldCount];
                                for (int i = 0; i < r.FieldCount; i++)
                                {
                                    values[i] = r.GetValue(i);
                                }
                                //r.GetValues(values); This way don't work through TransparentProxy
                                lock (table)
                                {
                                    table.Rows.Add(values);
                                }
                            }
                        }
                        _execWorker.ReportProgress(2, "ReaderExecuted");
                    }
                    while (r.NextResult());

                    if (!hasrows)
                    {
                        int affected = r.RecordsAffected;
                        if (affected == -1)
                            _execWorker.ReportProgress(3, "No records affected.");
                        else
                            _execWorker.ReportProgress(3, "Records affected: " + affected);
                    }
                    r.Close();
                    lineoffset += stm.Split('\n').Length;
                }
            }
            catch (Exception ex)
            {
                string msg = _connectionInfo.ConnectionProperties.FormatOwnException(lineoffset, ex);
                if (msg == "")
                    msg = ex.Message;
                _execWorker.ReportProgress(3, msg);
            }
            finally
            {
                _connectionInfo.ConnectionProperties.ConnectionMessage -= new nyaqt.Plugins.ConnectionMessageDelegate(ConnectionProperties_ConnectionMessage);
            }
        }

        void ConnectionProperties_ConnectionMessage(string text)
        {
            _execWorker.ReportProgress(3, text);
        }

        void execWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            string step = e.UserState as string;
            if (e.ProgressPercentage == 0)
            {
                if (ExecuteAsyncCompleted != null)
                    ExecuteAsyncCompleted.Invoke();
            }
            else if (e.ProgressPercentage == 1)
            {
                if (ExecuteAsyncNewResult != null)
                    ExecuteAsyncNewResult.Invoke(e.UserState as ExecuteNewResultEventArgs);
            }
            else if (e.ProgressPercentage == 2)
            {
                if (ExecuteAsyncRowFetchComplete != null)
                    ExecuteAsyncRowFetchComplete.Invoke();
            }
            else if (e.ProgressPercentage == 3)
            {
                string text = e.UserState as string;
                text = text.Replace("\r", "");
                text = text.Replace("\n", "\r\n");

                if (ExecuteAsyncMessage != null)
                    ExecuteAsyncMessage.Invoke(text);
            }
            else if (e.ProgressPercentage == -1)
            {
                System.ComponentModel.RunWorkerCompletedEventArgs args = new System.ComponentModel.RunWorkerCompletedEventArgs(null, null, true);
                execWorker_RunWorkerCompleted(this, args);
            }
        }

        void execWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            _endTime = DateTime.Now;
            if (e.Error != null)
                throw e.Error;

            if (End != null)
                End.Invoke();
        }

        public void RunAsistedAsync(string stm)
        {
        }

        public delegate void ConnectAsyncCompletedDelegate();
        public event ConnectAsyncCompletedDelegate ConnectAsyncCompleted;

        public delegate void ExecuteAsyncCompletedDelegate();
        public event ExecuteAsyncCompletedDelegate ExecuteAsyncCompleted;

        public class ExecuteNewResultEventArgs
        {
            public DataTable Schema;
            public DataTable Data;
        }
        public delegate void ExecuteAsyncNewResultDelegate(ExecuteNewResultEventArgs e);
        public event ExecuteAsyncNewResultDelegate ExecuteAsyncNewResult;

        public delegate void ExecuteAsyncRowFetchCompleteDelegate();
        public event ExecuteAsyncRowFetchCompleteDelegate ExecuteAsyncRowFetchComplete;

        public delegate void ExecuteAsyncMessageDelegate(string text);
        public event ExecuteAsyncMessageDelegate ExecuteAsyncMessage;

        public delegate void RunAsistedAsyncCompletedDelegate();
        public event RunAsistedAsyncCompletedDelegate RunAsistedAsyncCompleted;

        public delegate void StartDelegate();
        public event StartDelegate Start;

        public delegate void EndDelegate();
        public event EndDelegate End;

        internal void Test()
        {
            _execWorker.AbortTest();
        }
    }
}
